package butines.core;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.LinkedList;

public class InputManager implements Input, InputListener, KeyListener, MouseListener, MouseMotionListener {
	
	private static final int STATE_RELEASED = 0;
	private static final int STATE_PRESSED  = 1;
	private static final int STATE_WAITING_FOR_RELEASE = 2;
	
	private Component component;
	
	private LinkedList<InputEvent> events = new LinkedList<InputEvent>();
	
	private LinkedList<InputListener> listeners = new LinkedList<InputListener>();
	
	private int[] keys = new int[NUM_KEY];
	private int[] buttons = new int[NUM_MOUSE];
	
    private Cursor blankCursor;
    
	private int mouseX;
	private int mouseY;
	private int lastMouseX;
	private int lastMouseY;
	
	public InputManager(Component component) {
		this.component = component;
		createBlankCursor();
	}
	
	public void setCursor(Cursor cursor) {
		component.setCursor(cursor);
	}
	
	private void createBlankCursor() {
		if (blankCursor == null) {
			Image blank = Toolkit.getDefaultToolkit().getImage("");
			blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(blank, new Point(0, 0), "invisible");
		}
	}
	
	public void showCursor() {
		component.setCursor(Cursor.getDefaultCursor());
	}
	
	public void hiddenCursor() {
		if (component.getCursor() != blankCursor) {
			component.setCursor(blankCursor);
		}
	}
	
	public void init() {
		component.addKeyListener(this);
		component.addMouseListener(this);
		component.addMouseMotionListener(this);
		for (int i = 0; i < keys.length; i++) {
			keys[i] = STATE_RELEASED;
		}
		for (int i = 0; i < buttons.length; i++) {
			buttons[i] = STATE_RELEASED;
		}
	}
	
	public void destroy() {
		component.removeKeyListener(this);
		component.removeMouseListener(this);
		component.removeMouseMotionListener(this);
		for (int i = 0; i < keys.length; i++) {
			keys[i] = STATE_RELEASED;
		}
		for (int i = 0; i < buttons.length; i++) {
			buttons[i] = STATE_RELEASED;
		}
	}
	
	public boolean isKeyPressed(int keyCode) {
		if (keys[keyCode] != STATE_PRESSED) {
			return false;
		}
		keys[keyCode] = STATE_WAITING_FOR_RELEASE;
		return true;
	}
	
	public boolean isKeyDown(int keyCode) {
		return keys[keyCode] == STATE_PRESSED || keys[keyCode] == STATE_WAITING_FOR_RELEASE;
	}
	
	public boolean isButtonPressed(int button) {
		if (buttons[button] != STATE_PRESSED) {
			return false;
		}
		buttons[button] = STATE_WAITING_FOR_RELEASE;
		return true;
	}
	
	public boolean isButtonDown(int button) {
		return buttons[button] == STATE_PRESSED || buttons[button] == STATE_WAITING_FOR_RELEASE;
	}
	
	private synchronized void addEvent(InputEvent event) {
		if (event != null) {
			events.addLast(event);
		}
	}

	public void addListener(InputListener listener) {
		listeners.add(listener);
	}
	
	public void removeListener(InputListener listener) {
		listeners.remove(listener);
	}
	
	public synchronized void sendEvent(float tpf) {
		while (!events.isEmpty()) {
			InputEvent inputEvent = events.removeFirst();
			switch (inputEvent.getID()) {

				case KeyEvent.KEY_PRESSED: {
					keyPressed(inputEvent, tpf);
					break;
				}
				
				case KeyEvent.KEY_RELEASED: {
					keyReleased(inputEvent, tpf);
					break;
				}
				
				case MouseEvent.MOUSE_PRESSED: {
					buttonPressed(inputEvent, tpf);
					break;
				}
				
				case MouseEvent.MOUSE_RELEASED: {
					buttonReleased(inputEvent, tpf);
					break;
				}
				
				case MouseEvent.MOUSE_MOVED:
				case MouseEvent.MOUSE_DRAGGED: {
					mouseMoved(inputEvent, tpf);
					break;
				}
				
			}
		}
	}
	
	private void keyPressed(InputEvent inputEvent, float tpf) {
		KeyEvent keyEvent = (KeyEvent)inputEvent;
		int keyCode = keyEvent.getKeyCode();
		if (keys[keyCode] == STATE_RELEASED) {
			keys[keyCode] = STATE_PRESSED;
			onKey(keyCode, true, tpf);
		}
	}

	private void keyReleased(InputEvent inputEvent, float tpf) {
		KeyEvent keyEvent = (KeyEvent)inputEvent;
		int keyCode = keyEvent.getKeyCode();
		keys[keyCode] = STATE_RELEASED;
		onKey(keyCode, false, tpf);
	}
	
    private int getMouseButtonCode(int awtButton) {
        switch (awtButton) {
           case MouseEvent.BUTTON1: return BUTTON_1;
           case MouseEvent.BUTTON2: return BUTTON_2;
           case MouseEvent.BUTTON3: return BUTTON_3;
           default: return -1;
       }
   }
	
	private void buttonPressed(InputEvent inputEvent, float tpf) {
		MouseEvent mouseEvent = (MouseEvent)inputEvent;
		int awtButton = mouseEvent.getButton();
		int button = getMouseButtonCode(awtButton);
		if (buttons[button] == STATE_RELEASED) {
			buttons[button] = STATE_PRESSED;
			onButton(mouseX, mouseY, button, true, tpf);
		}
	}

	private void buttonReleased(InputEvent inputEvent, float tpf) {
		MouseEvent mouseEvent = (MouseEvent)inputEvent;
		int awtButton = mouseEvent.getButton();
		int button = getMouseButtonCode(awtButton);
		buttons[button] = STATE_RELEASED;
		onButton(mouseX, mouseY, button, false, tpf);
	}
	
	private void mouseMoved(InputEvent inputEvent, float tpf) {
		MouseEvent mouseEvent = (MouseEvent)inputEvent;
		lastMouseX = mouseX;
		lastMouseY = mouseY;
		mouseX = mouseEvent.getX();
		mouseY = mouseEvent.getY();
		onMove(mouseX, mouseY, mouseX - lastMouseX, mouseY - lastMouseY, tpf);
	}
		
	 public int getMouseX() {
		 return mouseX;
	 }

	 public int getMouseY() {
		 return mouseY;
	 }
	
	// implements KeyListener
	public void keyPressed(KeyEvent event) {
		addEvent(event);
		event.consume();
	}

	public void keyReleased(KeyEvent event) {
		addEvent(event);
		event.consume();
	}

	public void keyTyped(KeyEvent event) {
		// Ignored //
		event.consume();
	}
	
	// implements MouseListener
	public void mouseClicked(MouseEvent event) {
		// ignored //
		event.consume();
	}
	
	public void mouseEntered(MouseEvent event) {
		//
	}

	public void mouseExited(MouseEvent event) {
		//
	}

	public void mousePressed(MouseEvent event) {
		addEvent(event);
		event.consume();
	}

	public void mouseReleased(MouseEvent event) {
		addEvent(event);
		event.consume();
	}
	
	// implements MouseMotionListener
	public void mouseDragged(MouseEvent event) {
		addEvent(event);
		event.consume();
	}

	public void mouseMoved(MouseEvent event) {	
		addEvent(event);
		event.consume();
	}

	// implements InputListener
	public void onKey(int keyCode, boolean pressed, float tpf) {
		for (InputListener listener: listeners) {
			listener.onKey(keyCode, pressed, tpf);
		}
	}

	public void onButton(int x, int y, int button, boolean pressed, float tpf) {
		for (InputListener listener: listeners) {
			listener.onButton(x, y, button, pressed, tpf);
		}
	}

	public void onMove(int x, int y, int dx, int dy, float tpf) {
		for (InputListener listener: listeners) {
			listener.onMove(x, y, dx, dy, tpf);
		}
	}
		
}
